Análise das ontologias¶

Este notebook tem objetivo de analisar a expansão do AQE apenas com as ontologias comparando com não utilizar a expansão do AQE. Antes de rodar esse notebook é necessário ter o elasticsearch configurado com a base REGIS, bem como o AQE rodando. Ambas configurações devem ser setadas no .env da raíz deste projeto. A base de conhecimento que o AQE se conecta deve ter apenas os termos e relacionamentos das ontologias, removendo o conteúdo de qualquer outra origem.

Carregando libs e dados¶

In [1]:
import json 
import os

from dotenv import load_dotenv
import numpy as np
import pandas as pd
import plotly.express as px
import requests
from sklearn.model_selection import train_test_split

from utils.aqe_pso import AQEPSO
from utils.utils import create_metrics, create_ranking_dataset, \
    retrieve_from_elasticsearch

load_dotenv()
Out[1]:
True
In [2]:
with open("../data/regis_queries.json", "r") as json_file:
    regis_queries = json.loads(json_file.read())

certificate_path = "../PetrobrasCARootCorporativa.crt"

Realizando otimizações das ontologias¶

Primeiramente, vamos separar as queries do REGIS dataset em treino e teste de maneira estratificada, baseado no NDCG@24, com objetivo demanter a mesma distribuição dos NDCGs em treino e teste.

In [3]:
regis_ndcgs = pd.DataFrame({
    "query_id": [f"Q{i}" for i in range(1, 35)],
    "ndcg": [.7719, .9198, .7985, .7515, .5955, .6564, .9640, .9862, .9689, .9666, .5514, .8548, .9627, .9797, .4468, .3421, .8286, .8280, .6997, .7641, .7227, .9150, .9438, .8854, .9663, .7734, .9525, .9226, .8856, .8109, .5275, .8596, .6378, .6721]
}).assign(
    ndcg_bin = lambda row: (np.searchsorted(np.sort(row.ndcg), row.ndcg) / 4).astype(int)
)

X_train, X_test, y_train, y_test = train_test_split(
    regis_ndcgs.filter(items=["query_id"]),
    regis_ndcgs.filter(items=["ndcg"]),
    stratify=regis_ndcgs.filter(items=["ndcg_bin"]),
    test_size=0.25,
    random_state=1234
)

print(f"O NDCG@24 médio do treino é {y_train.ndcg.mean():.4f} e o do teste é {y_test.ndcg.mean():.4f}")
O NDCG@24 médio do treino é 0.7919 e o do teste é 0.8129

Agora vamos rodar a otimização para as relações e base de ontologia, bem como para a quantidade máxima de termos expandidos.

In [4]:
pso_handler = AQEPSO(
    params={
        "SYN": (0, 1),
        "age_of": (0, 1),
        "located_in": (0, 1),
        "crosses": (0, 1),
        "constituted_by": (0, 1),
        "has_age": (0, 1),
        "NER_ONTOLOGIES": (0, 1),
        "max_expanded_terms": (1, 20),
    },
    relation_keys=[
        "SYN",
        "age_of",
        "located_in",
        "crosses",
        "constituted_by",
        "has_age",
    ],
    source_keys=[
        "NER_ONTOLOGIES"
    ],
    we_keys=[],
    train_queries=X_train.query_id.tolist(),
    test_queries=X_test.query_id.tolist()
)

best_ndcg, best_params = pso_handler.execute_optimizer(
    iterations=5,
    n_particles=100,
    options = {'c1': 0.5, 'c2': 0.5, 'w': 0.9}
)
2023-07-12 15:46:52,932 - pyswarms.single.global_best - INFO - Optimize for 5 iters with {'c1': 0.5, 'c2': 0.5, 'w': 0.9}
pyswarms.single.global_best: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████|5/5, best_cost=-.794
2023-07-12 16:41:23,980 - pyswarms.single.global_best - INFO - Optimization finished | best cost: -0.7938591954940882, best pos: [0.56983339 0.38441317 0.26450677 0.43565119 0.22677467 0.56125026
 0.0241806  1.60903646]
In [5]:
print(f"O melhor NDCG@24 encontrado foi de {best_ndcg} com os seguintes parâmetros:")
best_params
O melhor NDCG@24 encontrado foi de 0.7938591954940882 com os seguintes parâmetros:
Out[5]:
{'NER_ONTOLOGIES': 0.5698333928141863,
 'SYN': 0.3844131665465531,
 'age_of': 0.2645067671515973,
 'constituted_by': 0.4356511936832812,
 'crosses': 0.22677466731845852,
 'has_age': 0.5612502563908,
 'located_in': 0.02418059911751813,
 'max_expanded_terms': 1.6090364571957159}

Consultando o AQE¶

Vamos agora consultar as queries do REGIS no AQE com os novos parâmetros.

In [6]:
max_expanded_terms = round(best_params["max_expanded_terms"])

regis_df = pd.DataFrame(
    regis_queries
)
regis_df["aqe_response"] = regis_df["title"].apply(
    lambda title: requests.get(
        f"{os.getenv('AQE_URL')}?query={title}&max_expanded_terms={max_expanded_terms}",
        verify=certificate_path
    ).text
)
regis_df.head()
Out[6]:
title query_id aqe_response
0 História da geoquímica na Petrobras Q1 História da geoquímica na Petrobras
1 Lógica fuzzy aplicada à industria do petróleo Q2 Lógica fuzzy aplicada à industria do petróleo
2 Simulação de reservatórios usando linhas de fluxo Q3 Simulação de reservatórios usando linhas de fluxo
3 Detecção de exsudações de óleo nas bacias de S... Q4 ((Detecção de exsudações de óleo nas bacias de...
4 Permeabilidade em Marlim Q5 ((Permeabilidade em Marlim) OR ((marlim^1.000 ...

Podemos ver que algumas queries permaneceram as mesmas. A não expansão foi devido a falta de termos na base de conhecimento, composta agora apenas das ontologias. Vejamos qual a proporção de queries que não foram expandidas:

In [7]:
len(regis_df.query("title == aqe_response")) / len(regis_df)
Out[7]:
0.38235294117647056

Podemos ver que 38,24% das queries não foram alteradas pelo AQE com apenas ontologias.

Criando métricas de ranking¶

Vamos agora criar as métricas de ranking sem AQE e com AQE com apenas ontologias.

In [8]:
cfg = {
    "elasticsearch": {
        "url": os.getenv("ELASTIC_SEARCH_URL"),
        "index": os.getenv("ELASTIC_SEARCH_INDEX"),
        "username": os.getenv("ELASTIC_SEARCH_USERNAME"),
        "password": os.getenv("ELASTIC_SEARCH_PASSWORD"),
        "certificate": certificate_path
    }
}
In [9]:
def get_metrics(df, col, cfg):
    queries = df.filter(
        items=["query_id", col]
    ).itertuples(
        index=False, name=None
    )
    queries = list(queries)

    ranking_result_df = retrieve_from_elasticsearch(queries, cfg, 24)
    ground_truth = pd.read_csv("../data/regis_ground_truth.csv")

    ranking_dataset = create_ranking_dataset(ranking_result_df, ground_truth)
    metrics_df = create_metrics(ranking_dataset, groupby_columns=["query_id"])

    return metrics_df
In [10]:
no_onto_metrics_df = get_metrics(regis_df, "title", cfg)
onto_metrics_df = get_metrics(regis_df, "aqe_response", cfg)
In [11]:
metrics_df = pd.concat([
    no_onto_metrics_df.assign(expansion_type = "Sem AQE"),
    onto_metrics_df.assign(expansion_type = "Com AQE baseado em ontologias")
])
metrics_df.head()
Out[11]:
query_id ndcg ap eval_prop expansion_type
0 Q1 0.766699 0.355878 0.944444 Sem AQE
1 Q10 0.950217 0.809482 1.000000 Sem AQE
2 Q11 0.733006 0.416667 1.000000 Sem AQE
3 Q12 0.761268 0.447358 0.902439 Sem AQE
4 Q13 0.974752 0.885727 0.928571 Sem AQE

Analisando as métricas¶

In [12]:
metrics_df.groupby(
    "expansion_type"
).agg(
    {"ndcg": "mean"}
).reset_index()
Out[12]:
expansion_type ndcg
0 Com AQE baseado em ontologias 0.781614
1 Sem AQE 0.760157

Podemos ver que a métrica geral aumentou de 0.760157 para 0.781614. Vale lembrar que esse valor de NDCG@24 foi ligeiramente diferente do reportado anteriormente, de 0.7939, tendo em vista que esse foi o de treinamento.

Vejamos o NDCG@24 por query:

In [13]:
def format_aqe_response(s, n=10):
    final_str = ""
    for i, e in enumerate(s.split()):
        if i > 0 and i % n == 0:
            e += "<br>"
        else:
            e += " "
        final_str += e
    return final_str.strip().strip("<br>")

data_viz = metrics_df.merge(
    regis_df, on="query_id"
)
data_viz["aqe_response"] = data_viz["aqe_response"].apply(format_aqe_response)

fig = px.bar(
    data_viz, x="query_id", y="ndcg",
    title="NDCG das queries",
    color="expansion_type",
    barmode="group",
    hover_data=["query_id", "ndcg", "title", "aqe_response"],
    labels={
        "query_id": "Query ID",
        "ndcg": "NDCG (Normalized Discounted Cumulative Gain)",
    }
).update_layout(xaxis={"categoryorder":"total descending"})
fig.show()

Podemos ver que as queries que mais se destacaram foram a Q17, Q7, Q12 e Q23. Inspecionando estas queries, podemos ver que elas expandiram utilizando termos sinônimos ou relacionados das bacias ou formações.

Conclusão¶

Pudemos ver que ao utilizar apenas as ontologias na base de conhecimento o NDCG@24 aumentou comparado a não utilizar o AQE. As queries que mais tiveram impacto positivo utilizaram sinônimos ou relacionados das bacias ou formações.